<?php

	namespace org\tekuna\plugin\simplesecurity;
	
	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\configuration\ConfigurationElement;
	use org\tekuna\core\configuration\ConfigurationException;
	use org\tekuna\core\context\Context;
	use org\tekuna\core\context\ApplicationContextAware;
	use org\tekuna\core\context\ContextInitialized;

	use org\tekuna\framework\context\ContextObjectBuilder;
	use org\tekuna\framework\action\ActionEvent;
	
	
	class SecurityManager implements ApplicationContextAware, ContextInitialized {
		
		const
			DEFAULT_USER_PROVIDER = 'org\\tekuna\\plugin\\simplesecurity\\ConfigUserProvider',
			AUTHENTICATED_USER_SESSION_KEY = 'simple-security.authenticated.user';
		
		private
			$objLogger,
			$objContext,
			$objConfiguration,
			$objUserProvider = NULL,
			$objAuthenticatedUser = NULL,
			
			$sAfterLoginAction,
			$sAfterLogoutAction,
			
			$sLoginAction = '/simple-security/login.html',
			$sLogoutAction = '/simple-security/logout.html',
			$sUnauthorizedAction = '/simple-security/unauthorized.html';

		
		public function initialize() {
			
			$this -> objLogger = Tekuna :: getLogger(__CLASS__);
			
			// get the security config
			$this -> objConfiguration = $this -> objContext -> getConfiguration();
			$objSecurityConfigElement = $this -> objConfiguration -> getRootElement() -> getFirstChildElement('simple-security', 'security-config');
			
			// load configuration
			$this -> loadUserProvider($objSecurityConfigElement);
			$this -> loadActions($objSecurityConfigElement);
			
			// load authenticated user
			$this -> loadAuthenticatedUser();
		}
			
		
		public function setApplicationContext(Context $objContext) {

			$this -> objContext = $objContext;
		}
		
		
		public function getApplicationContext() {
			
			return $this -> objContext;
		}
		
		
		private function loadUserProvider(ConfigurationElement $objSecurityConfigElement) {

			$sUserProviderClass = self :: DEFAULT_USER_PROVIDER;
			if ($objSecurityConfigElement -> hasAttribute('simple-security', 'user-provider')) {
				
				$sUserProviderClass = $objSecurityConfigElement -> getAttribute('simple-security', 'user-provider') -> getValue();
			}
			
			$objUserProvider = new $sUserProviderClass();
			
			if (! $objUserProvider instanceof UserProvider) {
				
				throw new SecurityException("The class '$sUserProviderClass' does not implement the interface 'org\\tekuna\\plugin\\simplesecurity\\UserProvider'.");
			}
			
			$objUserProvider -> initialize($this -> objContext);
			$this -> objUserProvider = $objUserProvider;
		}
		
		
		private function loadActions(ConfigurationElement $objSecurityConfigElement) {
		
			// load mandatory attributes
			$this -> sAfterLoginAction = $objSecurityConfigElement -> getAttribute('simple-security', 'after-login-action') -> getValue();
			$this -> sAfterLogoutAction = $objSecurityConfigElement -> getAttribute('simple-security', 'after-logout-action') -> getValue();
			
			// override login action if given
			if ($objSecurityConfigElement -> hasAttribute('simple-security', 'login-action')) {

				$this -> sLoginAction = $objSecurityConfigElement -> getAttribute('simple-security', 'login-action') -> getValue();
			}

			// override logout action if given
			if ($objSecurityConfigElement -> hasAttribute('simple-security', 'logout-action')) {

				$this -> sLogoutAction = $objSecurityConfigElement -> getAttribute('simple-security', 'logout-action') -> getValue();
			}
		
			// override unauthorized action if given
			if ($objSecurityConfigElement -> hasAttribute('simple-security', 'unauthorized-action')) {

				$this -> sUnauthorizedAction = $objSecurityConfigElement -> getAttribute('simple-security', 'unauthorized-action') -> getValue();
			}
		}
		
		
		private function loadAuthenticatedUser() {

			// start the session
			session_name('tekuna-simple-login');
			session_start();
			
			// check session contents
			if (isset($_SESSION[self :: AUTHENTICATED_USER_SESSION_KEY])) {
				
				$sLogin = $_SESSION[self :: AUTHENTICATED_USER_SESSION_KEY];
				$this -> objAuthenticatedUser = $this -> getUser($sLogin);
				$this -> objLogger -> info("loaded authenticated user '$sLogin'.");
			}
		}
		
		
		public function encryptPassword($sLogin, $sPassword) {
			
			return $this -> objUserProvider -> encryptPassword($sLogin, $sPassword);
		}
		
		
		public function getUser($sLogin) {
			
			return $this -> objUserProvider -> getUser($sLogin);
		}
		
		
		public function loginUser($sLogin, $sPassword) {
			
			// load user
			$objUser = $this -> getUser($sLogin);
			
			// check password
			if ($objUser -> encryptedPassword !== $this -> encryptPassword($sLogin, $sPassword)) {
				
				throw new SecurityException("The password for '$sLogin' is not correct.");
			}
			
			$this -> objLogger -> info("logged in user '$sLogin'.");
			
			// store in manager
			$this -> objAuthenticatedUser = $objUser;
			
			// store in session
			$_SESSION[self :: AUTHENTICATED_USER_SESSION_KEY] = $sLogin;
			
			return $objUser;
		}
		
		
		public function logoutUser() {
			
			if (is_object($this -> objAuthenticatedUser)) {

				$this -> objLogger -> info("logging out user '". $this -> objAuthenticatedUser -> login ."'.");
				$this -> objAuthenticatedUser = null;
			}
			
			unset($_SESSION[self :: AUTHENTICATED_USER_SESSION_KEY]);
		}
		
		
		public function getAuthenticatedUser() {
			
			return $this -> objAuthenticatedUser;
		}
		
		
		public function isUserAuthorized(User $objUser, ActionEvent $objActionEvent) {

			// check application level authorization
			$objApplicationElement = $this -> objConfiguration -> getRootElement();
			$bApplicationAuthorized = $this -> isUserAuthorizedAtElement($objUser, $objApplicationElement);
			
			// check component level authorization
			$objComponentElement = $objActionEvent -> getComponentElement();
			$bComponentAuthorized = $this -> isUserAuthorizedAtElement($objUser, $objComponentElement);
			
			// check action level authorization
			$objActionElement = $objActionEvent -> getActionElement();
			$bActionAuthorized = $this -> isUserAuthorizedAtElement($objUser, $objActionElement);
			
			// user must be authorized for all three levels
			return $bApplicationAuthorized & $bComponentAuthorized & $bActionAuthorized;
		}
		
		
		private function isUserAuthorizedAtElement(User $objUser, ConfigurationElement $objElement) {
			
			if ($objElement -> hasAttribute('simple-security', 'requiredRoles')) {
				
				// get the roles attribute
				$sRoles = trim($objElement -> getAttribute('simple-security', 'requiredRoles') -> getValue());
				
				if ($sRoles == '') {
					
					// if the roles attribute is empty, the user is always authorized
					return true;
				}
				
				// check all roles
				foreach (explode(',', $sRoles) as $sRole) {
					
					if ($objUser -> hasRole($sRole)) {
						
						return true;
					}
				}
				
				// user was not authenticated for any role
				return false;
			}
			else {
				
				// if there are no required roles defined, the user is authorized
				return true;
			}
		}

		
		public function getAfterLoginAction() {
			
			return $this -> sAfterLoginAction;
		}
		
		
		public function getAfterLogoutAction() {
			
			return $this -> sAfterLogoutAction;
		}
		
		
		public function getLoginAction() {
			
			return $this -> sLoginAction;
		}
		
		
		public function getLogoutAction() {
			
			return $this -> sLogoutAction;
		}
		
		
		public function getUnauthorizedAction() {
			
			return $this -> sUnauthorizedAction;
		}
	}
